#include "httpConn.hpp"

const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";
const char* doc_root = "/var/www/html";

int setNonblocking(int fd) {
    int oldOption = fcntl(fd, F_GETFL);
    int newOption = oldOption | O_NONBLOCK;
    fcntl(fd, F_SETFL, newOption);
    return oldOption;
}

void addFd(int epollFd, int fd, bool oneShot) {
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    if(oneShot) {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &event);
    setNonblocking(fd);
}

void removeFd(int epollFd, int fd) {
    epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

void modFd(int epollFd, int fd, int ev) {
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollFd, EPOLL_CTL_MOD, fd, &event);
}

int httpConn::m_user_count = 0;
int httpConn::m_epollFd = -1;

void httpConn::closeConn(bool real_close) {
    if(real_close && m_sockFd != -1) {
        removeFd(m_epollFd, m_sockFd);
        m_sockFd = -1;
        m_user_count--;
    }
}

void httpConn::init(int sockFd, const sockaddr_in* addr) {
    m_sockFd = sockFd;
    m_address = addr;
    int reuse = 1;
    setsockopt(m_sockFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    addFd(m_epollFd, sockFd, true);
    m_user_count++;

    init();
}

void httpConn::init() {
    m_check_state = CHECK_STATE_REQUESTLINE;
    m_linger = false;

    m_method = GET;
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_check_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;
    memset(m_read_buf, 0, READ_BUFFER_SIZE);
    memset(m_write_buf, 0, WRITE_BUFFER_SIZE);
    memset(m_real_file, 0, FILENAME_LEN);
}

httpConn::LINE_STATUS httpConn::parseLine() {
    char temp;
    for(; m_check_idx < m_read_idx; ++m_check_idx) {
        temp = m_raed_buf[m_check_idx];
        if(temp == '\r') {
            // 当前检查的位置在最后一个字节
            if(m_check_idx + 1 == m_read_idx) {
                return LINE_OPEN;
            }
            else if (m_raed_buf[m_check_idx + 1] == '\n') {
                m_raed_buf[m_check_idx++] = '\0'; // \r 变为 \0
                m_raed_buf[m_check_idx++] = '\0'; // \n 变为 \0
                return LINE_OK;
            }
            return LINE_BAD;
        }
        else if(temp = '\n') {
            if(m_check_idx > 1 && m_raed_buf[m_check_idx- 1] == '\r') {
                m_raed_buf[m_check_idx - 1] = '\0'; // \r 变为 \0
                m_raed_buf[m_check_idx++] = '\0'; // \n 变为 \0
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

bool httpConn::read() {
    if(m_read_idx >= READ_BUFFER_SIZE) {
        return false;
    }
    int bytes_read = 0;
    while(true) {
        bytes_read = recv(m_sockFd, m_raed_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);
        if(bytes_read == -1) {
            if(errno == EAGAIN || errno == EWOULDBLOCK) {
                break;
            }
            return false;
        }
        else if(bytes_read == 0) {
            return false;
        }
        m_read_idx += bytes_read;
    }
    return true;
} 

httpConn::HTTP_CODE httpConn::parseRequestLine(char* text) {
    m_url = strpbrk(text, " \t");
    if(!m_url) {
        return BAD_REQUEST;
    }
    *m_url++ = '\0';

    char* method = text;
    if(strcasecmp(method, "GET") == 0) {
        m_method = GET;
    }
    else {
        return BAD_REQUEST;
    }

    m_url += strspn(m_url, " \t");
    m_version = strpbrk(m_url, " \t");
    if(!m_version) {
        return BAD_REQUEST;
    }
    *m_version++ = '\0';
    m_version += strspn(m_version, " \t");
    if(strcasecmp(m_version, "HTTP/1.1") != 0) {
        return BAD_REQUEST;
    }
    if(strncasecmp(m_url, "http://", 7) == 0) {
        m_url += 7;
        m_url = strchr(m_url, '/');
    }
    if(!m_url || m_url[0] != '/') {
        return BAD_REQUEST;
    }
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

httpConn::HTTP_CODE httpConn::parseHeaders(char* text) {
    if(text[0] == '\0') {
        if(m_content_length != 0) {
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        return GET_REQUEST;
    }
    else if (strncasecmp(text, "Connection:", 11) == 0) {
        text += 11;
        text += strspn(text, " \t");
        if(strcasecmp(text, "keep-alive") == 0) {
            m_linger = true;
        }
    }
    else if (strncasecmp(text, "Content-Length:", 15) == 0) {
        text += 15;
        text += strspn(text, " \t");
        m_content_length = atol(text);
    }
    else if (strncasecmp(text, "Host:", 5) == 0) {
        text += 5;
        text += strspn(text, " \t");
        m_host = text;
    }
    else {
        cout << "oop! unknow header " << text << endl; 
    }
    return NO_REQUEST;
}

httpConn::HTTP_CODE httpConn::parseContent(char* text) {
    if(m_read_idx >= m_content_length + m_check_idx) {
        text[m_content_length] = '\0';
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

httpConn::HTTP_CODE httpConn::processRead() {
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char* text = 0;
    while((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || (line_status = parseLine()) == LINE_OK) {
        text = getLine();
        m_start_line = m_check_idx;
        cout << "got 1 http line:" << text << endl;

        switch(m_check_state) {
            case CHECK_STATE_REQUESTLINE: {
                ret = parseRequestLine(text);
                if(ret == BAD_REQUEST) {
                    return BAD_REQUEST;
                }
                break;
            }
            case CHECK_STATE_HEADER: {
                ret = parseHeaders(text);
                if(ret == BAD_REQUEST) {
                    return BAD_REQUEST;
                }
                else if (ret == GET_REQUEST) {
                    return doRequest();
                }
                break;
            }
            case CHECK_STATE_CONTENT: {
                ret = parseContent(text);
                if (ret == GET_REQUEST) {
                    return doRequest();
                }
                line_status = LINE_OPEN;
                break;
            }
            default: {
                return INTERNAL_ERROR;
            }
        }
    }
    return NO_REQUEST;
}

httpConn::HTTP_CODE httpConn::doRequest() {
    strcpy(m_real_file, doc_root);
    int len = strlen(doc_root);
    strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);
    if (stat(m_real_file, &m_file_stat) < 0) {
        return NO_RESOURCE;
    }
    if (!(m_file_stat.st_mode & S_IROTH)) {
        return FORBIDDEN_REQUEST;
    }
    if (S_ISDIR(m_file_stat.st_mode)) {
        return BAD_REQUEST;
    }
    int fd = open(m_real_file, O_RDONLY);
    m_file_address = (char*)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;
}

void httpConn::unmap() {
    if(m_file_address) {
        munmap(m_file_address, m_file_stat.st_size);
        m_file_address = 0;
    }
}

bool httpConn::write() {
    int temp = 0;
    int bytes_have_send = 0;
    int bytes_to_send = m_write_idx;
    if (bytes_to_send == 0) {
        modFd(m_epollFd, m_sockFd, EPOLLIN);
        init();
        return true;
    }
    while(1) {
        temp = writev(m_sockFd, m_iv, m_iv_count);
        if (temp <= -1) {
            if(errno == EAGAIN) {
                modFd(m_epollFd, m_sockFd, EPOLLIN);
                return true;
            }
            unmap();
            return false;
        }

        bytes_to_send -=temp;
        bytes_have_send += temp;
        if(bytes_to_send <= bytes_have_send) {
            unmap();
            if(m_linger) {
                init();
                modFd(m_epollFd, m_sockFd, EPOLLIN);
                return true;
            }
            else {
                modFd(m_epollFd, m_sockFd, EPOLLIN);
                return false;
            }
        }
    }
}

bool httpConn::addResponse(const char* format, ...) {
    if(m_write_idx >= WRITE_BUFFER_SIZE) {
        return false;
    }
    va_list arg_list;
    va_start(arg_list, format);
    int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);
    if(len >= WRITE_BUFFER_SIZE - 1 - m_write_idx) {
        return false;
    }
    m_write_idx += len;
    va_end(arg_list);
    return true;
}

bool httpConn::addStatusLine(int status, const char* title) {
    return addResponse("%s %d %s\r\n", "HTTP/1.1", status, title);
}

bool httpConn::addHeaders(int contentLength) {
    addContentLength(contentLength);
    addLinger();
    addBlankLine();
}

bool httpConn::addContentLength(int contentLength) {
    return addResponse("Content-Length: %d\r\n", contentLength);
}

bool httpConn::addLinger() {
    return addResponse("Connection: %s\r\n", m_linger ? "keep-alive" : "close");
}

bool httpConn::addBlankLine() {
    return addResponse("%s", "\r\n");
}

bool httpConn::addContent(const char* content) {
    return addResponse("%s", content);
}

bool httpConn::processWrite(HTTP_CODE ret) {
    switch(ret) {
        case INTERNAL_ERROR: {
            addStatusLine(500, error_500_title);
            addHeaders(strlen(error_500_form));
            if(!addContent(error_500_form)) {
                return false;
            }
            break;
        }
        case BAD_REQUEST: {
            addStatusLine(400, error_400_title); 
            addHeaders(strlen(error_400_form));
            if(!addContent(error_400_form)) {
                return false;
            }
            break;
        }
        case NO_RESOURCE: {
            addStatusLine(404, error_404_title);
            addHeaders(strlen(error_404_form));
            if(!addContent(error_404_form)) {
                return false
            }
            break;
        }
        case FORBIDDEN_REQUEST: {
            addStatusLine(403, error_403_title);
            addHeaders(strlen(error_403_form));
            if(!addContent(error_403_form)) {
                return false;
            }
            break;
        }
        case FILE_REQUEST: {
            addStatusLine(200, ok_200_title);
            if(m_file_stat.st_size != 0) {
                addHeaders(m_file_stat.st_size);
                m_iv[0].iov_base = m_write_buf;
                m_iv[0].iov_len = m_write_idx;
                m_iv[1].iov_base = m_file_address;
                m_iv[1].iov_len = m_file_stat.st_size;
                m_iv_count = 2;
                return true;
            }
            else {
                const char* ok_string = "<html><body></body></html>";
                addHeaders(strlen(ok_string));
                if(!addContent(ok_string)) {
                    return false;
                }
            }
        }
        default: {
            return false;
        }
    }
    m_iv[0].iov_base = m_write_buf;
    m_iv[0].iov_len = m_write_idx;
    m_iv_count = 1;
    return true;
}

void httpConn::process() {
    HTTP_CODE read_ret = processRead();
    if (read_ret == NO_REQUEST) {
        modFd(m_epollFd, m_sockFd, EPOLLIN);
        return;
    }
    
    bool write_ret = processWrite(read_ret);
    if(!write_ret) {
        closeConn();
    }

    modFd(m_epollFd, m_sockFd, EPOLLOUT);
}